package main

import (
	"fmt"
	"math"
	"sync"
	"time"

	"gitee.com/haifengat/goctp"
	ctp "gitee.com/haifengat/goctp/lnx"
)

/*appid:simnow_client_test
authcode:0000000000000000*/
var (
	investor = "008105"
	pwd      = "1"
	broker   = "9999"
	appid    = "simnow_client_test"
	authcode = "0000000000000000"
	ticks    sync.Map

	tradeFront = "tcp://180.168.146.187:10130"
	quoteFront = "tcp://180.168.146.187:10131"

	instrument  = "rb2210"
	lots        = 3  // 手数
	tailTimes   = 3  // 追单次数
	tailSecond  = 3  // 追单秒数
	offsetTicks = -2 // 追价跳数: 负数,反向加价,很难成交

	tailTimesTotal = 0 // 已追单次数

	tailOrders       sync.Map // 需要追单的委托
	stopLossOrders   sync.Map // 需要止损的 order:: 线程安全
	stopTicks        = 3      // 止损跳数
	takeProfitOrders sync.Map // 止盈 orderid
	takeTicks        = 2      // 止盈跳数

	t *ctp.Trade
	q *ctp.Quote
)

func main() {
	t = ctp.NewTrade()

	t.RegOnFrontConnected(func() {
		fmt.Println("trade connected")
		t.ReqLogin(investor, pwd, broker, appid, authcode)
	})

	/*	<error id="INVALID_DATA_SYNC_STATUS" value="1" prompt="CTP:不在已同步状态"/>
		<error id="INCONSISTENT_INFORMATION" value="2" prompt="CTP:会话信息不一致"/>
		<error id="INVALID_LOGIN" value="3" prompt="CTP:不合法的登录"/>
		<error id="USER_NOT_ACTIVE" value="4" prompt="CTP:用户不活跃"/>
		<error id="DUPLICATE_LOGIN" value="5" prompt="CTP:重复的登录"/>
		<error id="NOT_LOGIN_YET" value="6" prompt="CTP:还没有登录"/>
		<error id="NOT_INITED" value="7" prompt="CTP:还没有初始化"/>
		<error id="FRONT_NOT_ACTIVE" value="8" prompt="CTP:前置不活跃"/>
		<error id="LOGIN_FORBIDDEN" value="75" prompt="CTP:连续登录失败次数超限，登录被禁止"/>*/
	t.RegOnRspUserLogin(func(loginField *goctp.RspUserLoginField, info *goctp.RspInfoField) {
		// 委托过多/合约数量 导致返回时间比较久
		fmt.Println(loginField.TradingDay, ": ", info)
		// 合约数
		var instCnt int
		t.Instruments.Range(func(_, _ interface{}) bool {
			// fmt.Println(value.(*goctp.InstrumentField).InstrumentID)
			instCnt++
			return true
		})
		fmt.Println("合约数：", instCnt)
		// var orderCnt int
		// t.Orders.Range(func(key, value interface{}) bool {
		// 	orderCnt++
		// 	return true
		// })
		// fmt.Println("委托数量：", orderCnt)
	})

	// t.ReqConnect("tcp://180.168.146.187:10202")
	t.ReqConnect(tradeFront)

	for !t.IsLogin {
		time.Sleep(1 * time.Second)
	}

	// 打印持仓
	// fmt.Println("----------------持仓----------------------")
	// t.Positions.Range(func(key, value interface{}) bool {
	// 	posi := value.(*goctp.PositionField)
	// 	bs, _ := json.Marshal(posi)
	// 	fmt.Println(string(bs))
	// 	return true
	// })

	// 委托-错误
	t.RegOnErrRtnOrder(func(field *goctp.OrderField, info *goctp.RspInfoField) {
		fmt.Println("委托", field.OrderRef, "|错误：", info)
	})
	// 委托-相应
	t.RegOnRtnOrder(func(field *goctp.OrderField) {
	})
	//委托-成交
	t.RegOnRtnTrade(func(field *goctp.TradeField) {
		var dire = "买"
		if field.Direction == goctp.DirectionSell {
			dire = "卖"
		}
		var offset = "开"
		if field.OffsetFlag != goctp.OffsetFlagOpen {
			offset = "平"
		}
		fmt.Println("成交价格：", field.Price, "\t", dire, offset, "\t", field.Volume)
	})

	// 撤单成功
	t.RegOnRtnCancel(func(orderField *goctp.OrderField) {
		go tailOrder(orderField)
	})

	q = ctp.NewQuote()

	q.RegOnFrontConnected(func() {
		fmt.Println("quote connected")
		q.ReqLogin(investor, pwd, broker)
	})
	q.RegOnRspUserLogin(func(_ *goctp.RspUserLoginField, info *goctp.RspInfoField) {
		fmt.Printf("%+v", info)
		q.ReqSubscript(instrument) // 订阅指定合约行情
		// 订阅所有合约
		// t.Instruments.Range(func(key, value interface{}) bool {
		// 	q.ReqSubscript(key.(string))
		// 	return true
		// })
	})

	q.RegOnTick(func(tick *goctp.TickField) {
		// 保存行情
		ticks.Store(tick.InstrumentID, tick)
		// fmt.Println(tick.InstrumentID, ":", tick.LastPrice, ",", tick.UpperLimitPrice)
		// 止盈
		go takeProfit(tick)
		// 止损
		go stopLoss(tick)
	})

	// q.ReqConnect("tcp://180.168.146.187:10212")
	q.ReqConnect(quoteFront)

	// 行情登录
	time.Sleep(5 * time.Second)
	tick, ok := ticks.Load(instrument)
	if !ok {
		fmt.Println("无行情：", instrument)
		return
	}
	// 跌板发单,不会成交=>测试止损需先成交
	orderID := t.ReqOrderInsert(instrument, goctp.DirectionSell, goctp.OffsetFlagOpen, tick.(*goctp.TickField).BidPrice1, lots)
	// 止损止赢
	stopLossOrders.Store(orderID, nil)
	takeProfitOrders.Store(orderID, nil)
	// 追单
	// tailOrder(orderID)

	select {}
}

// 开启追单
func startTailOrder(orderID string) {
	time.Sleep(time.Duration(tailSecond) * time.Second)
	// fmt.Println("3 秒后挂价追单") // 挂价可能不会立即成交
	fmt.Println(tailSecond, " 秒后跳价追单")
	// 追单前先撤原来的委托; 撤单成功则表示委托未成交;
	tailOrders.Store(orderID, nil)
	t.ReqOrderAction(orderID)
}

// 追单
func tailOrder(orderField *goctp.OrderField) {
	if _, ok := tailOrders.Load(fmt.Sprintf("%d_%s", orderField.SessionID, orderField.OrderRef)); !ok {
		return
	}
	// 取合约的 tick
	inst, ok := t.Instruments.Load(orderField.InstrumentID)
	if !ok {
		fmt.Println("合约错误: ", orderField.InstrumentID)
	}
	// 取 tick
	tick, ok := ticks.Load(orderField.InstrumentID)
	if !ok {
		fmt.Println("行情错误: ", orderField.InstrumentID)
	}
	// 偏移 2 跳
	offset := float64(offsetTicks) * inst.(*goctp.InstrumentField).PriceTick
	var price float64
	if tailTimesTotal >= tailTimes { // 达到3次后板价追单
		if orderField.Direction == goctp.DirectionBuy {
			price = tick.(*goctp.TickField).UpperLimitPrice
		} else {
			price = tick.(*goctp.TickField).LowerLimitPrice
		}
		t.ReqOrderInsert(orderField.InstrumentID, orderField.Direction, orderField.OffsetFlag, price, orderField.VolumeLeft)
	} else {
		// 撤单成功后再追单
		if orderField.Direction == goctp.DirectionBuy {
			price = tick.(*goctp.TickField).BidPrice1 + offset // 在价下买入, 会一直不成交, 以测试追单是否正确.
		} else {
			price = tick.(*goctp.TickField).AskPrice1 - offset
		}
		// 板价控制
		price = math.Min(tick.(*goctp.TickField).UpperLimitPrice, price) // 避免超过板价
		price = math.Max(tick.(*goctp.TickField).LowerLimitPrice, price) // 避免超过板价

		newID := t.ReqOrderInsert(orderField.InstrumentID, orderField.Direction, orderField.OffsetFlag, price, orderField.VolumeLeft)
		fmt.Println("(oncancel)追单 id: ", newID, " 价格: ", price)

		// 追单
		time.Sleep(time.Duration(tailSecond) * time.Second)
		// fmt.Println("3 秒后挂价追单") // 挂价可能不会立即成交
		fmt.Println(tailSecond, " 秒后跳价追单")
		tailTimesTotal++

		// 追单前先撤原来的委托
		t.ReqOrderAction(newID)
	}
}

// stopLoss 止损
func stopLoss(tick *goctp.TickField) {
	stopLossOrders.Range(func(orderID, _ interface{}) bool {
		field, _ := t.Orders.Load(orderID)
		of := field.(*goctp.OrderField)
		if of.InstrumentID != tick.InstrumentID { // 合约不匹配
			return true
		}
		if of.OrderStatus == goctp.OrderStatusAllTraded || of.OrderStatus == goctp.OrderStatusPartTradedQueueing { // 全成或部成
			inst, _ := t.Instruments.Load(of.InstrumentID)
			// 止损价
			if of.Direction == goctp.DirectionBuy {
				stopPrice := of.TradePrice - float64(stopTicks)*inst.(*goctp.InstrumentField).PriceTick
				fmt.Println("long 止损价: ", stopPrice)
				if tick.LastPrice <= stopPrice { // 止损
					// LastPrice::上面计算的stopPrice可能不是priceTick的倍数
					// LowerLimitPrice::板价止损
					t.ReqOrderInsert(of.InstrumentID, goctp.DirectionSell, goctp.OffsetFlagCloseToday, tick.LowerLimitPrice, of.VolumeTraded)
					// 止损后处理
					stopLossOrders.Delete(orderID)
				}
			} else {
				stopPrice := of.TradePrice + float64(stopTicks)*inst.(*goctp.InstrumentField).PriceTick
				fmt.Println("short 止损价: ", stopPrice, "当前价: ", tick.LastPrice)
				if tick.LastPrice >= stopPrice {
					// 止损后处理::避免重复执行
					stopLossOrders.Delete(orderID)
					t.ReqOrderInsert(of.InstrumentID, goctp.DirectionBuy, goctp.OffsetFlagCloseToday, tick.UpperLimitPrice, of.VolumeTraded)
				}
			}
		}
		return true
	})
}

// takeProfit 对价止盈(追单)
func takeProfit(tick *goctp.TickField) {
	takeProfitOrders.Range(func(orderID, _ interface{}) bool {
		field, _ := t.Orders.Load(orderID)
		of := field.(*goctp.OrderField)
		if of.InstrumentID != tick.InstrumentID { // 合约匹配
			return true
		}
		if of.OrderStatus == goctp.OrderStatusAllTraded {
			inst, _ := t.Instruments.Load(of.InstrumentID)
			// 止损价
			if of.Direction == goctp.DirectionBuy {
				takePrice := of.TradePrice + float64(takeTicks)*inst.(*goctp.InstrumentField).PriceTick
				fmt.Println("long 止盈价: ", takePrice)
				if tick.LastPrice >= takePrice {
					// 止赢后处理
					stopLossOrders.Delete(orderID)
					takeProfitOrders.Delete(orderID)
					orderID := t.ReqOrderInsert(of.InstrumentID, goctp.DirectionSell, goctp.OffsetFlagCloseToday, tick.BidPrice1, of.VolumeTraded)
					// 开启追单
					startTailOrder(orderID)
				}
			} else {
				takePrice := of.TradePrice - float64(takeTicks)*inst.(*goctp.InstrumentField).PriceTick
				fmt.Println("short 止赢价: ", takePrice, "当前价: ", tick.LastPrice)
				if tick.LastPrice <= takePrice {
					// 止赢后处理::避免重复执行
					stopLossOrders.Delete(orderID)
					takeProfitOrders.Delete(orderID)
					// 用 last 测试追单
					orderID := t.ReqOrderInsert(of.InstrumentID, goctp.DirectionBuy, goctp.OffsetFlagCloseToday, tick.AskPrice1, of.VolumeTraded)
					// 开启追单
					startTailOrder(orderID)
				}
			}
		}
		return true
	})
}
